/**
 * WorkflowTemplate class for managing parameterized ComfyUI workflows
 */

import type {
  WorkflowTemplateData,
  TemplateMetadata,
  ComfyUIWorkflow,
  ParameterSchema,
  ParameterValues,
  ValidationResult
} from '../types';
import { WorkflowInstance } from './WorkflowInstance';
import { validateParameters } from '../utils/validation';

/**
 * Represents a parameterized workflow template
 */
export class WorkflowTemplate {
  private readonly _metadata: TemplateMetadata;
  private readonly _workflow: ComfyUIWorkflow;
  private readonly _parameters: Record<string, ParameterSchema>;

  /**
   * Creates a new WorkflowTemplate instance
   * @param data - Template data including metadata, workflow, and parameters
   */
  constructor(data: WorkflowTemplateData) {
    this._metadata = { ...data.metadata };
    this._workflow = JSON.parse(JSON.stringify(data.workflow)) as ComfyUIWorkflow;
    this._parameters = { ...data.parameters };

    // Validate template structure
    this.validateTemplate();
  }

  /**
   * Gets the template metadata
   */
  get metadata(): TemplateMetadata {
    return { ...this._metadata };
  }

  /**
   * Gets the workflow definition
   */
  get workflow(): ComfyUIWorkflow {
    return JSON.parse(JSON.stringify(this._workflow)) as ComfyUIWorkflow;
  }

  /**
   * Gets the parameter schemas
   */
  get parameters(): Record<string, ParameterSchema> {
    return { ...this._parameters };
  }

  /**
   * Gets the template ID
   */
  get id(): string {
    return this._metadata.id;
  }

  /**
   * Gets the template name
   */
  get name(): string {
    return this._metadata.name;
  }

  /**
   * Gets the template description
   */
  get description(): string | undefined {
    return this._metadata.description;
  }

  /**
   * Gets the template category
   */
  get category(): string | undefined {
    return this._metadata.category;
  }

  /**
   * Gets the template tags
   */
  get tags(): string[] {
    return [...(this._metadata.tags || [])];
  }

  /**
   * Validates the provided parameters against the template schema
   * @param parameters - Parameters to validate
   * @returns Validation result
   */
  validate(parameters: ParameterValues): ValidationResult {
    return validateParameters(parameters, this._parameters);
  }

  /**
   * Creates a workflow instance with the provided parameters
   * @param parameters - Parameter values to apply
   * @returns WorkflowInstance with parameters applied
   * @throws Error if parameters are invalid
   */
  createInstance(parameters: ParameterValues): WorkflowInstance {
    const validation = this.validate(parameters);
    if (!validation.valid) {
      const errorMessages = validation.errors.map(e => `${e.path}: ${e.message}`).join(', ');
      throw new Error(`Invalid parameters: ${errorMessages}`);
    }

    return new WorkflowInstance(this, parameters);
  }

  /**
   * Gets the parameter schema for a specific parameter
   * @param parameterName - Name of the parameter
   * @returns Parameter schema or undefined if not found
   */
  getParameterSchema(parameterName: string): ParameterSchema | undefined {
    return this._parameters[parameterName];
  }

  /**
   * Gets all required parameter names
   * @returns Array of required parameter names
   */
  getRequiredParameters(): string[] {
    return Object.entries(this._parameters)
      .filter(([, schema]) => schema.required === true)
      .map(([name]) => name);
  }

  /**
   * Gets all optional parameter names
   * @returns Array of optional parameter names
   */
  getOptionalParameters(): string[] {
    return Object.entries(this._parameters)
      .filter(([, schema]) => schema.required !== true)
      .map(([name]) => name);
  }

  /**
   * Checks if the template has a specific parameter
   * @param parameterName - Name of the parameter to check
   * @returns True if parameter exists
   */
  hasParameter(parameterName: string): boolean {
    return parameterName in this._parameters;
  }

  /**
   * Gets default values for all parameters that have defaults
   * @returns Object with default parameter values
   */
  getDefaultValues(): ParameterValues {
    const defaults: ParameterValues = {};
    for (const [name, schema] of Object.entries(this._parameters)) {
      if (schema.default !== undefined) {
        defaults[name] = schema.default;
      }
    }
    return defaults;
  }

  /**
   * Creates a copy of the template with updated metadata
   * @param metadata - Partial metadata to update
   * @returns New WorkflowTemplate instance
   */
  withMetadata(metadata: Partial<TemplateMetadata>): WorkflowTemplate {
    return new WorkflowTemplate({
      metadata: { ...this._metadata, ...metadata },
      workflow: this._workflow,
      parameters: this._parameters
    });
  }

  /**
   * Converts the template to a JSON-serializable object
   * @returns Template data object
   */
  toJSON(): WorkflowTemplateData {
    return {
      metadata: this.metadata,
      workflow: this.workflow,
      parameters: this.parameters
    };
  }

  /**
   * Creates a WorkflowTemplate from a JSON object
   * @param data - Template data object
   * @returns New WorkflowTemplate instance
   */
  static fromJSON(data: WorkflowTemplateData): WorkflowTemplate {
    return new WorkflowTemplate(data);
  }

  /**
   * Validates the template structure
   * @private
   */
  private validateTemplate(): void {
    // Validate metadata
    if (!this._metadata.id || typeof this._metadata.id !== 'string') {
      throw new Error('Template metadata must have a valid id');
    }
    if (!this._metadata.name || typeof this._metadata.name !== 'string') {
      throw new Error('Template metadata must have a valid name');
    }

    // Validate workflow
    if (!this._workflow || typeof this._workflow !== 'object') {
      throw new Error('Template must have a valid workflow object');
    }

    // Validate parameters
    if (!this._parameters || typeof this._parameters !== 'object') {
      throw new Error('Template must have a valid parameters object');
    }

    // Validate parameter schemas
    for (const [name, schema] of Object.entries(this._parameters)) {
      if (!schema.type) {
        throw new Error(`Parameter '${name}' must have a type`);
      }
      if (!['string', 'number', 'boolean', 'array', 'object'].includes(schema.type)) {
        throw new Error(`Parameter '${name}' has invalid type: ${schema.type}`);
      }
    }
  }
}
